//===============================================================================
// Microsoft patterns & practices Enterprise Library Contribution
// Logging Application Block
//===============================================================================
// License: http://www.codeplex.com/entlibcontrib/Project/License.aspx
// Author: Randy
//===============================================================================

using System;
using System.Collections.Generic;
using System.Text.RegularExpressions;
using Microsoft.Practices.EnterpriseLibrary.Common.Configuration;
using Microsoft.Practices.EnterpriseLibrary.Logging;
using Microsoft.Practices.EnterpriseLibrary.Logging.Configuration;
using EntLibContrib.Logging.Configuration;

namespace EntLibContrib.Logging.Parser
{
	/// <summary>
	/// Contains functions for reading log files and log setting from configuration files.
	/// </summary>
	public class CategoryLogFileReader : LogEntryParser
	{
		#region Constructor
		/// <summary>
		/// Constructor to initialize a new instance of the class.
		/// </summary>
		public CategoryLogFileReader()
			: base()
		{
		}
		/// <summary>
		/// Constructor to initialize a new instance of the class.
		/// </summary>
		/// <param name="theTimeStampParser">The time stamp parser to use</param>
		public CategoryLogFileReader(ITimeStampParser theTimeStampParser)
			: base(theTimeStampParser)
		{
		}
		#endregion

		#region GetConfigTraceListenerFileName

		/// <summary>
		/// Get the log file name from the specified category name in the logging settings configuration file.
		/// </summary>
		/// <param name="configurationFilePath">configuration file with the logging settings</param>
		/// <param name="categoryName">category name</param>
		/// <returns>name of the log file</returns>
		public static string GetConfigTraceListenerFileName(string configurationFilePath, string categoryName)
		{
			LoggingSettings loggingSettings = GetLoggingSettings(configurationFilePath);
			TraceSourceData categoryTraceSourceData = GetConfigTraceSourceData(loggingSettings, categoryName);
			return GetFileTraceListener(loggingSettings, categoryTraceSourceData).FileName;
		}

		/// <summary>
		/// Get the log file name from the specified category name in the app settings configuration file.
		/// </summary>
		/// <param name="categoryName">category name</param>
		/// <returns>name of the log file</returns>
		public static string GetConfigTraceListenerFileName(string categoryName)
		{
			IConfigurationSource configurationSource = ConfigurationSourceFactory.Create();
			LoggingSettings loggingSettings = CategoryLogFileReader.GetLoggingSettings(configurationSource);
			TraceSourceData categoryTraceSourceData = GetConfigTraceSourceData(loggingSettings, categoryName);
			return GetFileTraceListener(loggingSettings, categoryTraceSourceData).FileName;
		}

		#endregion

		#region GetLoggingSettings

		/// <summary>
		/// Get the logging settings from the specified configuration file.
		/// </summary>
		/// <param name="configurationFilePath">configuration file with the logging settings</param>
		/// <returns>the logging settings</returns>
		public static LoggingSettings GetLoggingSettings(string configurationFilePath)
		{
			return GetLoggingSettings(new FileConfigurationSource(configurationFilePath));
		}
		/// <summary>
		/// Get the logging settings from the specified configuration source.
		/// </summary>
		/// <param name="configurationSource">logging configuration source</param>
		/// <returns>the logging settings</returns>
		public static LoggingSettings GetLoggingSettings(IConfigurationSource configurationSource)
		{
			LoggingSettings loggingSettings =
					configurationSource.GetSection(LoggingSettings.SectionName)
					as LoggingSettings;
			if (loggingSettings == null)
				throw new LoggingSectionNotDefinedException();
			return loggingSettings;
		}

		#endregion

		#region GetConfigTraceSourceData
		/// <summary>
		/// Get the configuration source data from the specified category name in the logging settings.
		/// </summary>
		/// <param name="loggingSettings">logging configuration settings</param>
		/// <param name="categoryName">category name</param>
		/// <returns>
		/// configuration source data for the category
		/// </returns>
		public static TraceSourceData GetConfigTraceSourceData(LoggingSettings loggingSettings, string categoryName)
		{
			TraceSourceData categoryTraceSourceData = loggingSettings.TraceSources.Get(categoryName);
			if (categoryTraceSourceData == null)
				throw new LoggingCategoryNotDefinedException();
			return categoryTraceSourceData;
		}
		#endregion

		#region GetFileTraceListener
		/// <summary>
		/// Iteration for each of the trace listeners in the category.
		/// </summary>
		/// <param name="loggingSettings">logging configuration settings</param>
		/// <param name="categoryTraceSourceData">configuration source data</param>
		/// <returns>each of the trace listeners</returns>
		public static IEnumerable<TraceListenerData> GetTraceListeners(LoggingSettings loggingSettings, TraceSourceData categoryTraceSourceData)
		{
			// Loop through all trace listeners in the logging settings
			foreach (TraceListenerData logTraceListener in loggingSettings.TraceListeners)
			{
				// Loop through all trace listeners named for this category
				foreach (TraceListenerReferenceData categoryTraceListener in categoryTraceSourceData.TraceListeners)
				{
					// Use the category's trace listener name to identify the corresponding trace listener
					if (categoryTraceListener.Name == logTraceListener.Name)
					{
						yield return logTraceListener;
					}
				}
			}
		}
		/// <summary>
		/// Get the trace listener that implements the file properties.
		/// </summary>
		/// <param name="loggingSettings">logging configuration settings</param>
		/// <param name="categoryTraceSourceData">configuration source data</param>
		/// <returns>
		/// the trace listener that implements the file properties
		/// </returns>
		/// <remarks>
		/// Categories can reference multiple trace listeners, so
		/// find the best match based on similarity between the category name and trace listener name
		/// </remarks>
		public static IFileTraceListenerData GetFileTraceListener(LoggingSettings loggingSettings, TraceSourceData categoryTraceSourceData)
		{
			IFileTraceListenerData exactMatch = null;
			IFileTraceListenerData alikeMatch = null;
			IFileTraceListenerData otherMatch = null;
			// Loop through all trace listeners in the category
			foreach (TraceListenerData logTraceListener in GetTraceListeners(loggingSettings, categoryTraceSourceData))
			{
				// Only want trace listeners that have file properties i.e. FileName, Footer, Formatter, Header, Name
				IFileTraceListenerData fileData = FileTraceListenerData.GetFileData(logTraceListener);
				if (fileData != null)
				{
					if (categoryTraceSourceData.Name == logTraceListener.Name)
					{
						exactMatch = fileData;
					}
					else if (categoryTraceSourceData.Name.Contains(logTraceListener.Name) ||
							logTraceListener.Name.Contains(categoryTraceSourceData.Name))
					{
						alikeMatch = fileData;
					}
					else
					{
						otherMatch = fileData;
					}
				}
			}
			if (exactMatch != null)
				return exactMatch;
			if (alikeMatch != null)
				return alikeMatch;
			if (otherMatch != null)
				return otherMatch;
			throw new LoggingFileTraceListenerNotFoundException();
		}
		#endregion

		#region ConcatCompleteTemplate
		/// <summary>
		/// Concatenate the complete template text with header and footer.
		/// </summary>
		/// <param name="loggingSettings">logging configuration settings</param>
		/// <param name="fileTraceListenerData">logging file trace listener configuration data with the common properties</param>
		/// <returns>
		/// the complete template text with header and footer
		/// </returns>
		public static string ConcatCompleteTemplate(LoggingSettings loggingSettings, IFileTraceListenerData fileTraceListenerData)
		{
			ITemplateTextFormatterData textFormatter = GetTextFormatterData(loggingSettings, fileTraceListenerData);
			return RegularExpressionBuilder.ConcatCompleteTemplate(
					fileTraceListenerData.Header,
					textFormatter.Template,
					fileTraceListenerData.Footer);
		}
		#endregion

		#region GetTextFormatterData
		/// <summary>
		/// Get the logging text formatter configuration data for the logging trace listener.
		/// </summary>
		/// <param name="loggingSettings">logging configuration settings</param>
		/// <param name="fileTraceListenerData">logging file trace listener configuration data with the common properties</param>
		/// <returns>
		/// logging text formatter configuration data
		/// </returns>
		public static ITemplateTextFormatterData GetTextFormatterData(LoggingSettings loggingSettings, IFileTraceListenerData fileTraceListenerData)
		{
			if (String.IsNullOrEmpty(fileTraceListenerData.Formatter))
				throw new LoggingFormatterNotDefinedException();
			FormatterData theFormatter = loggingSettings.Formatters.Get(fileTraceListenerData.Formatter);
			ITemplateTextFormatterData templateFormatter = GetTextFormatterData(theFormatter);
			if (templateFormatter == null)
				throw new LoggingTextFormatterNotFoundException();
			return templateFormatter;
		}
		/// <summary>
		/// Get the logging template text formatter configuration data.
		/// </summary>
		/// <param name="theFormatter">configuration settings for a log formatter</param>
		/// <returns>
		/// logging text formatter configuration data
		/// </returns>
		public static ITemplateTextFormatterData GetTextFormatterData(FormatterData theFormatter)
		{
			ITemplateTextFormatterData templateFormatter = theFormatter as ITemplateTextFormatterData;
			if (templateFormatter == null)
			{
				TextFormatterData textFormatter = theFormatter as TextFormatterData;
				if (textFormatter != null)
				{
					templateFormatter = new ExposeTextFormatterData(textFormatter);
				}
			}
			return templateFormatter;
		}
		/// <summary>
		/// Expose the properties of the <see cref="TextFormatterData"/>.
		/// </summary>
		private class ExposeTextFormatterData : ITemplateTextFormatterData
		{
			private TextFormatterData myData;

			/// <summary>
			/// Constructor to initialize a new instance of the class.
			/// </summary>
			/// <param name="theData">The data.</param>
			public ExposeTextFormatterData(TextFormatterData theData)
			{
				myData = theData;
			}

			/// <summary>
			/// Name of the trace listener instance.
			/// </summary>
			/// <value>The name.</value>
			public string Name
			{
				get { return myData.Name; }
			}

			/// <summary>
			/// Template containing tokens to replace by the text formatter.
			/// </summary>
			/// <value>The template.</value>
			public string Template
			{
				get { return myData.Template; }
			}
		}
		#endregion

		#region IEnumerable Category Names
		/// <summary>
		/// Iterates through each category name in the app configuration logging settings.
		/// </summary>
		/// <returns>
		/// an enumerator that iterates through each category name
		/// </returns>
		public static IEnumerable<string> AppCategoryNames()
		{
			IConfigurationSource configurationSource = ConfigurationSourceFactory.Create();
			LoggingSettings loggingSettings = CategoryLogFileReader.GetLoggingSettings(configurationSource);
			foreach (string categoryName in LogCategoryNames(loggingSettings))
			{
				yield return categoryName;
			}
		}

		/// <summary>
		/// Iterates through each category name in the logging configuration settings.
		/// </summary>
		/// <param name="configurationFilePath">configuration file with the logging settings</param>
		/// <returns>
		/// an enumerator that iterates through each category name
		/// </returns>
		public static IEnumerable<string> ConfigCategoryNames(string configurationFilePath)
		{
			LoggingSettings loggingSettings = GetLoggingSettings(configurationFilePath);
			foreach (TraceSourceData tsd in loggingSettings.TraceSources)
			{
				yield return tsd.Name;
			}
		}

		/// <summary>
		/// Iterates through each category name in the logging configuration settings.
		/// </summary>
		/// <param name="loggingSettings">logging configuration settings</param>
		/// <returns>
		/// an enumerator that iterates through each category name
		/// </returns>
		private static IEnumerable<string> LogCategoryNames(LoggingSettings loggingSettings)
		{
			foreach (TraceSourceData tsd in loggingSettings.TraceSources)
			{
				yield return tsd.Name;
			}
		}

		#endregion

		#region IEnumerable Parse Log

		/// <summary>
		/// Iterates through each log entry using the specified trace listener in configuration file to build the template.
		/// </summary>
		/// <param name="configurationFilePath">configuration file with the logging settings</param>
		/// <param name="categoryName">category name</param>
		/// <param name="logFileName">name of the log file to parse, or null to use the one specified in the configuration</param>
		/// <param name="filter">null for all or the filter range of interest</param>
		/// <returns>
		/// an enumerator that iterates through each log entry in the filter range
		/// </returns>
		public IEnumerable<LogEntry> ParseLogFile(string configurationFilePath, string categoryName, string logFileName, Filter filter)
		{
			LoggingSettings loggingSettings = GetLoggingSettings(configurationFilePath);
			TraceSourceData categoryTraceSourceData = GetConfigTraceSourceData(loggingSettings, categoryName);
			IFileTraceListenerData fileTraceListenerData = GetFileTraceListener(loggingSettings, categoryTraceSourceData);
			if (logFileName == null)
				logFileName = fileTraceListenerData.FileName;
			string completeTemplate = ConcatCompleteTemplate(loggingSettings, fileTraceListenerData);
			string logFileText = FileIO.ReadAllText(logFileName);
			IEnumerable<LogEntry> parsedLogFileText = ParseLogText(completeTemplate, logFileText, filter);
			foreach (LogEntry ent in parsedLogFileText)
			{
				yield return ent;
			}
		}

		/// <summary>
		/// Iterates through each log entry that matches the template.
		/// </summary>
		/// <param name="template">log entry template for creating the regular expression</param>
		/// <param name="logFileText">log file text</param>
		/// <param name="filter">null for all or the filter range of interest</param>
		/// <returns>
		/// an enumerator that iterates through each log entry in the filter range
		/// </returns>
		public IEnumerable<LogEntry> ParseLogText(string template, string logFileText, Filter filter)
		{
			Regex rex = RegularExpressionBuilder.DoBuildRegularExpressionFromTemplate(template);
			foreach (Match m in rex.Matches(logFileText))
			{
				LogEntry ent = DeserializeLogEntry(m);
				if (filter != null)
				{
					if (filter.Match(ent))
					{
						yield return ent;
					}
				}
				else
				{
					yield return ent;
				}
			}
		}

		#endregion
	}
}
